package vip.aster.system.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import vip.aster.common.constant.CacheConstants;
import vip.aster.common.constant.Constants;
import vip.aster.common.constant.enums.LoginOperationEnum;
import vip.aster.common.constant.enums.StatusEnum;
import vip.aster.common.constant.enums.SuperAdminEnum;
import vip.aster.common.constant.enums.SysConfigEnum;
import vip.aster.common.exception.BusinessException;
import vip.aster.common.utils.CacheUtils;
import vip.aster.common.utils.RSAEncrypt;
import vip.aster.common.utils.TimeUtil;
import vip.aster.framework.i18n.MessageUtils;
import vip.aster.framework.security.config.TokenStoreCache;
import vip.aster.framework.security.entity.SecurityUser;
import vip.aster.framework.security.entity.UserDetail;
import vip.aster.framework.security.mobile.MobileAuthenticationToken;
import vip.aster.framework.security.utils.TokenUtils;
import vip.aster.system.entity.SysTenant;
import vip.aster.system.service.*;
import vip.aster.system.vo.SysAccountLoginVO;
import vip.aster.system.vo.SysMobileLoginVO;
import vip.aster.system.vo.SysTokenVO;
import vip.aster.system.vo.SysUserVO;
import vip.aster.tenant.constants.TenantConstants;
import vip.aster.tenant.utils.TenantUtils;

import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * SysAuthServiceImpl
 *
 * @author Aster
 * @since 2023/11/30 16:51
 */
@Slf4j
@Service
@AllArgsConstructor
public class SysAuthServiceImpl implements SysAuthService {
    private SysCaptchaService sysCaptchaService;
    private SysLogLoginService sysLogLoginService;
    private SysConfigService sysConfigService;
    private TokenStoreCache tokenStoreCache;
    private AuthenticationManager authenticationManager;
    private SysUserService sysUserService;
    private SysConfigService configService;
    private PasswordEncoder passwordEncoder;
    private SysTenantService tenantService;

    @Override
    public SysTokenVO loginByAccount(SysAccountLoginVO login) {
        // 验证码效验
        boolean flag = sysCaptchaService.validate(login.getKey(), login.getCaptcha());
        if (!flag) {
            // 保存登录日志
            sysLogLoginService.save(null, login.getUsername(), Constants.FAIL, LoginOperationEnum.CAPTCHA_FAIL.getValue(), login.getTenantId());
            throw new BusinessException(MessageUtils.message("auth.captcha.error"));
        }
        // 检验登录失败次数
        Integer retryCount = checkRetryCount(login);

        // 校验租户
        if (TenantUtils.isEnable()) {
            // 校验租户
            this.checkTenant(login.getTenantId());
            // 设置租户ID
            TenantUtils.setDynamic(login.getTenantId());
            // 校验租户下的用户是否存在
            SysUserVO sysUser = sysUserService.getUserByTenant(login.getTenantId(), login.getUsername());
            if (sysUser == null) {
                throw new BusinessException(MessageUtils.message("auth.login.tenant.error"));
            }
        }

        Authentication authentication;
        try {
            // 解密密码
            String privateKey = CacheUtils.get(CacheConstants.SECRET_KEY, CacheConstants.PRIVATE_KEY, String.class);
            String password = RSAEncrypt.decrypt(login.getPassword(), privateKey);
            // 用户认证
            authentication = authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(login.getUsername(), password));
            // 登陆成功，清除失败次数
            CacheUtils.deleteIfPresent(CacheConstants.PWD_ERR_CNT_KEY, login.getUsername());
        } catch (BadCredentialsException e) {
            // 失败次数+1
            retryCount = retryCount == null ? 1 : retryCount + 1;
            CacheUtils.set(CacheConstants.PWD_ERR_CNT_KEY, login.getUsername(), retryCount, Constants.RETRY_EXPIRATION, TimeUnit.MINUTES);
            throw new BusinessException(MessageUtils.message("auth.login.account.error"));
        } catch (Exception e) {
            throw new BusinessException(MessageUtils.message("auth.login.decrypt.error"));
        }

        // 用户信息
        UserDetail user = (UserDetail) authentication.getPrincipal();

        // 生成 accessToken
        String accessToken = TokenUtils.generator();

        // 保存用户信息到缓存
        tokenStoreCache.saveUser(accessToken, user);

        return new SysTokenVO(accessToken);
    }

    /**
     * 检验登录失败次数
     * @param login 登录信息
     * @return 失败次数
     */
    private Integer checkRetryCount(SysAccountLoginVO login) {
        // 检验登录失败次数
        Integer retryCount = CacheUtils.get(CacheConstants.PWD_ERR_CNT_KEY, login.getUsername(), Integer.class);
        // 最大重试次数
        String maxRetryCount = sysConfigService.selectConfigByKey(CacheConstants.PWD_ERR_CNT_KEY);

        assert maxRetryCount != null;
        if (retryCount != null && retryCount >= Integer.parseInt(maxRetryCount)) {
            String msg = MessageUtils.message("auth.retry.error");
            throw new BusinessException(StrUtil.format(msg, maxRetryCount, Constants.RETRY_EXPIRATION));
        }
        return retryCount;
    }

    @Override
    public SysTokenVO loginByMobile(SysMobileLoginVO login) {
        // 校验租户
        this.checkTenant(login.getTenantId());

        Authentication authentication;
        try {
            // 用户认证
            authentication = authenticationManager.authenticate(
                    new MobileAuthenticationToken(login.getMobile(), login.getCode()));
        } catch (BadCredentialsException e) {
            throw new BusinessException(MessageUtils.message("auth.login.mobile.error"));
        }

        // 用户信息
        UserDetail user = (UserDetail) authentication.getPrincipal();

        // 生成 accessToken
        String accessToken = TokenUtils.generator();

        // 保存用户信息到缓存
        tokenStoreCache.saveUser(accessToken, user);

        return new SysTokenVO(accessToken);
    }

    @Override
    public boolean sendCode(String mobile) {
        // 生成6位验证码
        String code = RandomUtil.randomNumbers(6);

        SysUserVO user = sysUserService.getUserByMobile(mobile);
        if (user == null) {
            throw new BusinessException(MessageUtils.message("auth.login.mobile.register"));
        }

        //TODO 发送短信 smsApi.sendCode(mobile, "code", code)
        return false;
    }

    @Override
    public void logout(String accessToken) {
        // 1.获取用户信息
        UserDetail user = tokenStoreCache.getUser(accessToken);
        if (ObjectUtil.isNull(user)) {
            return;
        }
        String userId = user.getId();

        // 2.清除租户
        String tenantId = TenantUtils.getTenantId();
        if (TenantUtils.isEnable() && SuperAdminEnum.YES.getValue().equals(user.getSuperAdmin())) {
            // 超级管理员 登出清除动态租户
            TenantUtils.clearDynamic();
        }

        // 3.删除用户信息
        tokenStoreCache.deleteUser(accessToken);

        // 4.保存登录日志
        sysLogLoginService.save(userId, user.getUsername(), Constants.SUCCESS, LoginOperationEnum.LOGOUT_SUCCESS.getValue(), tenantId);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void resetPassword(List<String> idList) {
        if (CollUtil.isEmpty(idList)) {
            return;
        }

        // 用户信息
        UserDetail user = SecurityUser.getUser();
        assert user != null;
        if (!TenantUtils.isEnable() && !SuperAdminEnum.YES.getValue().equals(user.getSuperAdmin())) {
            throw new BusinessException(MessageUtils.message("auth.super.reset.error"));
        }
        String value = configService.selectConfigByKey(SysConfigEnum.PASSWORD.getValue());
        String password = passwordEncoder.encode(value);

        for (String userId : idList) {
            sysUserService.updatePassword(userId, password);
        }
    }

    @Override
    public String generateSecretKey() throws NoSuchAlgorithmException {
        String publicKey = CacheUtils.get(CacheConstants.SECRET_KEY, CacheConstants.PUBLIC_KEY, String.class);
        if (StrUtil.isBlank(publicKey)) {
            Map<String, String> encryptMap = RSAEncrypt.genKeyPair();
            publicKey = encryptMap.get(CacheConstants.PUBLIC_KEY);
            String privateKey = encryptMap.get(CacheConstants.PRIVATE_KEY);
            // 公钥
            CacheUtils.set(CacheConstants.SECRET_KEY, CacheConstants.PUBLIC_KEY, publicKey);
            // 私钥
            CacheUtils.set(CacheConstants.SECRET_KEY, CacheConstants.PRIVATE_KEY, privateKey);
        }
        return publicKey;
    }

    @Override
    public void checkTenant(String tenantId) {
        if (!TenantUtils.isEnable()) {
            return;
        }
        if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
            return;
        }
        if (StrUtil.isBlank(tenantId)) {
            throw new BusinessException(MessageUtils.message("tenant.number.not.blank"));
        }
        SysTenant tenant = tenantService.getById(tenantId);
        if (ObjectUtil.isNull(tenant)) {
            log.info("登录租户：{} 不存在.", tenantId);
            throw new BusinessException(MessageUtils.message("tenant.not.exists"));
        } else if (StatusEnum.DISABLE.getValue().equals(tenant.getStatus())) {
            log.info("登录租户：{} 已被停用.", tenantId);
            throw new BusinessException(MessageUtils.message("tenant.blocked"));
        } else if (ObjectUtil.isNotNull(tenant.getExpireTime())
                && new Date().after(TimeUtil.localDateTimeToDate(tenant.getExpireTime()))) {
            log.info("登录租户：{} 已超过有效期.", tenantId);
            throw new BusinessException(MessageUtils.message("tenant.expired"));
        }

    }

}
